package com.naiterui.ehp.bs.im.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.bo.im.DoctorsMedicalRecordBO;
import com.naiterui.ehp.bp.bo.im.FollowUpImMsgBO;
import com.naiterui.ehp.bp.bo.im.MessageSendResultBO;
import com.naiterui.ehp.bp.bo.im.PatientMsgSendBO;
import com.naiterui.ehp.bp.bo.im.PatientMsgSendParamBO;
import com.naiterui.ehp.bp.bo.im.RecomSendDTO;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.ImConstants;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.ResponseVO;
import com.naiterui.ehp.bp.support.security.HeaderConstant;
import com.naiterui.ehp.bp.support.utils.JsonMapper;
import com.naiterui.ehp.bp.utils.beans.BeanUtil;
import com.naiterui.ehp.bp.utils.biz.AppJumpProtocol;
import com.naiterui.ehp.bp.utils.net.RestHttpUtil;
import com.naiterui.ehp.bs.im.config.MessageConfig;
import com.naiterui.ehp.bs.im.config.RemoterConfig;
import com.naiterui.ehp.bs.im.dto.MessageSendResultDTO;
import com.naiterui.ehp.bs.im.dto.RecomRemindDTO;
import com.naiterui.ehp.bs.im.exception.ExceptionCodes;
import com.naiterui.ehp.bs.im.exception.NaiteruiIMException;
import com.naiterui.ehp.bs.im.feign.EmrFeginClient;
import com.naiterui.ehp.bs.im.feign.PatientFeginClient;
import com.naiterui.ehp.bs.im.network.protocl.Chater;
import com.naiterui.ehp.bs.im.network.protocl.CustomizedMessage;
import com.naiterui.ehp.bs.im.network.protocl.CustomizedMessage.HrefContent;
import com.naiterui.ehp.bs.im.network.protocl.CustomizedMessage.HrefContent.Params;
import com.naiterui.ehp.bs.im.network.protocl.DoctorsMedicalRecord;
import com.naiterui.ehp.bs.im.network.protocl.Extend;
import com.naiterui.ehp.bs.im.network.protocl.FollowUpImMsg;
import com.naiterui.ehp.bs.im.network.protocl.Media;
import com.naiterui.ehp.bs.im.network.protocl.Message;
import com.naiterui.ehp.bs.im.network.protocl.RecommandDrug;
import com.naiterui.ehp.bs.im.network.protocl.RecommandDrugItem;
import com.naiterui.ehp.bs.im.network.protocl.Session;
import com.naiterui.ehp.bs.im.network.protocl.SessionDetails;
import com.naiterui.ehp.bs.im.network.transcode.IMessageTransCoder;
import com.naiterui.ehp.bs.im.network.transfer.DoctorTransfer;
import com.naiterui.ehp.bs.im.remote.IRemoter;
import com.naiterui.ehp.bs.im.repository.mongo.MessageDao;
import com.naiterui.ehp.bs.im.repository.mongo.SessionDao;
import com.naiterui.ehp.bs.im.service.IMessageService;
import com.naiterui.ehp.bs.im.service.message.handler.DoctorMessageHandler;
import com.naiterui.ehp.bs.im.service.message.handler.MessageHandler;
import com.naiterui.ehp.bs.im.service.message.handler.PatientMessageHandler;
import com.naiterui.ehp.bs.im.service.message.handler.SessionHandler;
import com.naiterui.ehp.bs.im.utils.Constants;
import com.naiterui.ehp.bs.im.vo.SessionVO;
import com.naiterui.ehp.bs.im.vo.dto.RecomUserInfoDTO;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;

@Service
public class MessageService implements IMessageService {

    private static final Logger LOGGER = LoggerFactory.getLogger(MessageService.class);

    /**
     * 审核通过文案
     */
    private static final String DRUG_PASS_TEXT = "处方已通过审核，患者可正常下单.";

    /**
     * 审核不通过文案
     */
    private static final String DRUG_NOT_PASS_TEXT = "处方未通过审核：";

    /**
     * 已超时
     */
    private static final String DRUG_EXPIRED_TEXT = "已失效：";

    /**
     * 发送病历需要支付会话
     */
    private static final Integer NEED_PAY_CONSULT = 0;
    /**
     * 发送病历成功
     */
    private static final Integer MEDICAL_RECORD_SEND_SUCCESS = 1;

    /**
     * 待发送病历缓存时间
     */
    private static final int PATIENTS_RECORD_TOBE_SEND_EXPIRE_TIME = 24 * 60 * 60;

    private final SessionHandler sessionHandler;
    private final MessageHandler messageHandler;
    private final MessageDao messageDao;
    private final SessionDao sessionDao;
    private final DoctorMessageHandler doctorMessageHandler;
    private final DoctorTransfer doctorTransfer;
    private final MessageConfig messageConfig;
    private final RemoterConfig remoterConfig;
    private final IRemoter remoter;
    private final EmrFeginClient emrFeginClient;
    private final IMessageTransCoder messageTransCoder;
    private final PatientMessageHandler patientMessageHandler;
    private final PatientFeginClient patientFeginClient;

    @Autowired
    public MessageService(SessionHandler sessionHandler, MessageHandler messageHandler, MessageDao messageDao, SessionDao sessionDao,
                          @Qualifier(value = "doctorMessageHandler") DoctorMessageHandler doctorMessageHandler, DoctorTransfer doctorTransfer,
                          MessageConfig messageConfig, RemoterConfig remoterConfig, IRemoter remoter, EmrFeginClient emrFeginClient, IMessageTransCoder messageTransCoder, PatientMessageHandler patientMessageHandler, PatientFeginClient patientFeginClient) {
        this.sessionHandler = sessionHandler;
        this.messageHandler = messageHandler;
        this.messageDao = messageDao;
        this.sessionDao = sessionDao;
        this.doctorMessageHandler = doctorMessageHandler;
        this.doctorTransfer = doctorTransfer;
        this.messageConfig = messageConfig;
        this.remoterConfig = remoterConfig;
        this.remoter = remoter;
        this.emrFeginClient = emrFeginClient;
        this.messageTransCoder = messageTransCoder;
        this.patientMessageHandler = patientMessageHandler;
        this.patientFeginClient = patientFeginClient;
    }

    @Override
    public MessageSendResultDTO sendRecommend(RecomSendDTO recomSend) throws BusinessException {
        MessageSendResultDTO result = new MessageSendResultDTO();
        /*
         * 如果是附带处方的病历 则走发送病历流程
         */
        if (recomSend.getDoctorsMedicalRecord() != null && recomSend.getDoctorsMedicalRecord().getDoctorId() != null) {
            recomSend.getDoctorsMedicalRecord().setRecommandId(recomSend.getRecommendId());
            DoctorsMedicalRecordBO doctorsMedicalRecordBO = recomSend.getDoctorsMedicalRecord();
            doctorsMedicalRecordBO.setSend(recomSend.getSend());
            MessageSendResultBO messageSendResultBo = sendDoctorsMedicalRecord(doctorsMedicalRecordBO);
            MessageSendResultDTO resultDTO = new MessageSendResultDTO();
            BeanUtils.copyProperties(messageSendResultBo, resultDTO);
            return resultDTO;
        }

        // 生成一条新的视频消息
        Message<RecommandDrug> message = new Message<>();

        Chater from = new Chater();
        from.setId(recomSend.getDoctorId());

        Chater to = new Chater();
        to.setId(recomSend.getPatientId());

        RecommandDrug drug = new RecommandDrug();
        List<RecommandDrugItem> items = new ArrayList<RecommandDrugItem>();
        if (CollectionUtil.isNotEmpty(recomSend.getItems())) {
            BeanUtil.copyList(recomSend.getItems(), items, RecommandDrugItem.class);
        }
        //处方是否有效标识-正常
        drug.setInvalid(RecommandDrug.RECOMMAND_DRUG_VALID);
        drug.setItems(items);
        drug.setRecommandId(recomSend.getRecommendId());
        drug.setRecommandStatus(recomSend.getSend() ? RecommandDrug.RECOMMAND_TYPE_NO_CHECK : RecommandDrug.RECOMMAND_TYPE_IN_CHECK);
        drug.setRequireId(recomSend.getRequireId());
        drug.setShowPublicFee(recomSend.getShowPublicFee());
        drug.setRecomType(recomSend.getRecomType());

        //3.1 阳光化需求去掉诊金和随访积分
        //drug.setConsultFee(recomSend.getConsultFee());
        //drug.setVisitFee(recomSend.getVisitFee());

        message.setType(Constants.MESSAGE_TYPE_DRUG);
        message.setFrom(from);
        message.setTo(to);
        message.setRelation(Constants.MESSAGE_RESOURCE_DOCTOR);
        message.setInquirerId(recomSend.getInquirerId());
        message.setSendTime(System.currentTimeMillis());
        message.setSysMsgType(recomSend.getSend() ? Message.SYS_MSG_TYPE_CONSUMER : Message.SYS_MSG_TYPE_SYSTEM);
        message.setVisualType(recomSend.getSend() ? Message.MESSAGE_VISUAL_TYPE_ALL : Message.MESSAGE_VISUAL_TYPE_DOCTOR);
        message.setContent(drug);

        String sessionKey = sessionHandler.getSessionKey(recomSend.getDoctorId(), recomSend.getPatientId());
        // 是否有存在的会话
        boolean isSession = RedisUtil.keyOps().existsKey(sessionKey);
        // 是否首次绑定
        boolean isFirstBind = RedisUtil.keyOps().existsKey(CommonConstant.PATIENT_PERSONAL_DOCTOR_FIRST_BIND + recomSend.getDoctorId() + "_" + recomSend.getPatientId());
        SessionVO vo = new SessionVO();
        LOGGER.info("医生给患者推送处方是否需要开会话，consultType：{}， isSession：{}, isFirstBind：{}", recomSend.getConsultType(), isSession, isFirstBind);
        // 图文问诊和有会话的或者首次绑定才会进行会话处理
        if (CommonConstant.ConsultType.GRAPHIC == recomSend.getConsultType() && (isSession || isFirstBind)) {
            vo = sessionHandler.handleSession(message, true);
            if (vo != null && vo.isFinished()) {
                return result;
            }
        }

        messageHandler.handleMessage(message);

        result.setBeginTime(vo.getBeginTime());
        result.setConsultSessionId(vo.getConsultSessionId());
        result.setMessageId(message.getId());
        result.setSendTime(message.getSendTime());
        result.setSessionId(vo.getSessionId());
        result.setNewSessionFlag(vo.getNewSessionFlag());
        result.setConsultPayType(vo.getConsultPayType());
        result.setConsultSourceType(vo.getConsultSourceType());
        return result;
    }

    @Override
    public void sendRecommend(RecomRemindDTO recomRemind) throws BusinessException {

        Integer auditResult = recomRemind.getAuditResult();

        if (auditResult == RecommandDrug.RECOMMAND_TYPE_PASS) {
            LOGGER.info("drug is passed, drug id is {}", recomRemind.getRecommendId());
            // 如果审核通过
            checkPass(recomRemind.getRecommendId(), auditResult);
        } else if(auditResult== RecommandDrug.RECOMMAND_TYPE_EXPIRED){
            LOGGER.info("drug is expired, drug id is {}", recomRemind.getRecommendId());
            // 处方超时
            checkExpired(recomRemind.getRecommendId(), auditResult, recomRemind.getAuditDesc());
        } else {
            LOGGER.info("drug is not passed, drug id is {}", recomRemind.getRecommendId());
            // 审核不通过
            checkNotPass(recomRemind.getRecommendId(), auditResult, recomRemind.getAuditDesc());
        }
    }

    private void checkPass(long recommandId, int status) throws BusinessException {
        // 当前时间
        long sendTime = System.currentTimeMillis();

        String auditDesc = DRUG_PASS_TEXT;

        Message drugMessage = messageDao.findDrugByRecommandId(recommandId);
        // 如果消息推荐用药类型不是普通推荐，则消息只为患者可见
        Integer recomType = Optional.ofNullable(drugMessage).map(m -> ((RecommandDrug)m.getContent()).getRecomType())
            .orElse(RecommandDrug.RECOM_TYPE_MANNUL);
        Integer visualType = ObjectUtil.equal(RecommandDrug.RECOM_TYPE_MANNUL, recomType)
            ? Message.MESSAGE_VISUAL_TYPE_ALL : Message.MESSAGE_VISUAL_TYPE_PATIENT;
        // 审核不通过标记：消息双方可见，消息类型为用户消息
        Message<?> message = messageDao.findAndModifyByDrug(recommandId, status, auditDesc, sendTime, visualType,
            Message.SYS_MSG_TYPE_CONSUMER);

        LOGGER.info("处方审核通过=====================: {}", JSONUtil.toJsonStr(message));
        // 校准消息发送时间
        message.setSendTime(sendTime);

        boolean sendDoctorFlag = true;
        if (message.getType() == Constants.MESSAGE_TYPE_DOCTORS_MEDICALRECORD) {
            ((DoctorsMedicalRecord) message.getContent()).setCheckingStatus(status);
            ((DoctorsMedicalRecord) message.getContent()).setAuditDesc(auditDesc);
        } else {
            RecommandDrug drug = (RecommandDrug) message.getContent();
            drug.setRecommandStatus(status);
            drug.setAuditDesc(auditDesc);
            sendDoctorFlag = ObjectUtil.equal(drug.getRecomType(), RecommandDrug.RECOM_TYPE_MANNUL);
        }
        Session session = message.getSession();
        LOGGER.info("审核session信息====:{}", session != null);
        // 视频问诊的处方病历 不开会话
        if (session != null) {
            // 进行session处理
            sessionHandler.handleSession(message);
        }

        // 通过 审核后给患者发消息
        doctorMessageHandler.sendMessage(message);
        if (session != null) {
            // 推药处方和规划
            doctorMessageHandler.afterSendMessage(message);
        }
        if (session != null) {
            Long endTime = sessionHandler.getSessionEndTime(session.getSessionId());
            message.getSession().setEndTime(endTime);
        }
        // 同步审核 后代消息到医生端
        if (sendDoctorFlag) {
            doctorMessageHandler.sendSyncMessage(message);
        }
        if (session != null) {
            sessionHandler.updateContains(session.getSessionId(), SessionDetails.CONTAIN_RECOM_YES, null);
        }

    }

    private void checkNotPass(long recommandId, int status, String auditDesc) throws BusinessException {
        // 当前时间
        long sendTime = System.currentTimeMillis();

        auditDesc = DRUG_NOT_PASS_TEXT + auditDesc;

        // 审核不通过标记：消息仅医生可见，消息类型为系统消息
        Message<?> message = messageDao.findAndModifyByDrug(recommandId, status, auditDesc, sendTime, Message.MESSAGE_VISUAL_TYPE_DOCTOR,
                Message.SYS_MSG_TYPE_SYSTEM);
        LOGGER.info("处方审核不通过=====================: {}", JSONUtil.toJsonStr(message));
        boolean sendDoctorFlag = true;
        if (message.getType() == Constants.MESSAGE_TYPE_DOCTORS_MEDICALRECORD) {
            ((DoctorsMedicalRecord) message.getContent()).setCheckingStatus(status);
            ((DoctorsMedicalRecord) message.getContent()).setAuditDesc(auditDesc);
            // 同步审核 后代消息到医生端
            doctorMessageHandler.sendSyncMessage(message);
        } else {
            RecommandDrug drug = (RecommandDrug) message.getContent();
            drug.setRecommandStatus(status);
            drug.setAuditDesc(auditDesc);
            sendDoctorFlag = ObjectUtil.equal(drug.getRecomType(), RecommandDrug.RECOM_TYPE_MANNUL);
        }
        // 校准消息发送时间
        message.setSendTime(sendTime);
        Session session = message.getSession();
        LOGGER.info("审核不通过session信息====:{}", session != null);
        // 视频问诊的处方病历 不开会话
        if (session != null) {
            // 审核不通过session处理
            sessionHandler.handleSession(message);
        }
        if (session != null) {
            Long endTime = sessionHandler.getSessionEndTime(session.getSessionId());
            message.getSession().setEndTime(endTime);
        }
        // 同步审核 后代消息到医生端
        if (sendDoctorFlag) {
            doctorMessageHandler.sendSyncMessage(message);
        }

        if (session != null) {
            sessionHandler.updateContains(session.getSessionId(), SessionDetails.CONTAIN_RECOM_NO, null);
        }
    }

    private void checkExpired(long recommandId, int status, String auditDesc) throws BusinessException {
        // 当前时间
        long sendTime = System.currentTimeMillis();
        // 审核不通过标记：消息仅医生可见，消息类型为系统消息
        Message<?> message = messageDao.findAndModifyByDrug(recommandId, status, auditDesc, sendTime, Message.MESSAGE_VISUAL_TYPE_DOCTOR,
                Message.SYS_MSG_TYPE_SYSTEM);
        LOGGER.info("处方已失效=====================: {}", JSONUtil.toJsonStr(message));
        boolean sendDoctorFlag = true;
        if (message.getType() == Constants.MESSAGE_TYPE_DOCTORS_MEDICALRECORD) {
            ((DoctorsMedicalRecord) message.getContent()).setCheckingStatus(status);
            ((DoctorsMedicalRecord) message.getContent()).setAuditDesc(auditDesc);
            // 同步审核 后代消息到医生端
            doctorMessageHandler.sendSyncMessage(message);
        } else {
            RecommandDrug drug = (RecommandDrug) message.getContent();
            drug.setRecommandStatus(status);
            drug.setAuditDesc(auditDesc);
            sendDoctorFlag = ObjectUtil.equal(drug.getRecomType(), RecommandDrug.RECOM_TYPE_MANNUL);
        }
        // 校准消息发送时间
        message.setSendTime(sendTime);
        Session session = message.getSession();
        LOGGER.info("审核不通过session信息====:{}", session != null);
        // 视频问诊的处方病历 不开会话
        if (session != null) {
            // 审核不通过session处理
            sessionHandler.handleSession(message);
        }
        if (session != null) {
            Long endTime = sessionHandler.getSessionEndTime(session.getSessionId());
            message.getSession().setEndTime(endTime);
        }
        // 同步审核 后代消息到医生端
        if (sendDoctorFlag) {
            doctorMessageHandler.sendSyncMessage(message);
        }

        if (session != null) {
            sessionHandler.updateContains(session.getSessionId(), SessionDetails.CONTAIN_RECOM_NO, null);
        }

    }

    @Override
    public Integer sendPatientsMedicalRecord(Long doctorId, Long patientId, String caseId, SessionVO sessionVo) throws BusinessException {
        Message<CustomizedMessage> medicalRecordMessage = new Message<>();// 新建病历对象
        medicalRecordMessage.setType(Constants.MESSAGE_TYPE_PATEINTS_MEDICAL_RECORD);
        RecomUserInfoDTO userInfo;
        try {
            userInfo = remoter.getUserInfo(doctorId, patientId, false);
        } catch (Exception e1) {
            LOGGER.error("发送患者病历,查询医生、患者信息异常,doctorId:{},patientId:{}", doctorId, patientId);
            throw new NaiteruiIMException(ExceptionCodes.MEDICAL_RECORD_SEND_QUERY_USERINFO_ERROR);
        }
        Chater pt = new Chater(patientId, userInfo.getPatient().getName());
        Chater doctor = new Chater(doctorId, userInfo.getDoctor().getName());
        medicalRecordMessage.setFrom(pt);
        medicalRecordMessage.setTo(doctor);
        medicalRecordMessage.setInquirerId(Long.parseLong(RedisUtil.hashOps().hget(CommonConstant.CONSULT_PATIENT_AIM_INQUIRER,
                String.join("_", doctorId.toString(), patientId.toString()))));
        medicalRecordMessage.setRelation(Constants.MESSAGE_RESOURCE_PATIENT);
        medicalRecordMessage.setSendTime(System.currentTimeMillis());
        medicalRecordMessage.setSysMsgType(Message.SYS_MSG_TYPE_CONSUMER);
        medicalRecordMessage.setVisualType(Message.MESSAGE_VISUAL_TYPE_DOCTOR);
        if (sessionVo != null) {
            Session session = new Session();
            session.setSessionId(sessionVo.getSessionId());
            session.setBeginTime(sessionVo.getBeginTime());
            if (sessionVo.getConsultPayStatus() != null) {
                session.setConsultPayType(sessionVo.getConsultPayStatus());
            }
            medicalRecordMessage.setSession(session);
        }

        // 验证患者账户状态
        Object sourceStatus = RedisUtil.valueOps().getObject(Constants.CACHE_KEY_DIS_PATIENT + patientId);
        int status = sourceStatus == null ? 0 : NumberUtils.toInt(sourceStatus.toString());
        // 账号状态(1,禁用；0,正常)
        if (status == 1) {
            throw new NaiteruiIMException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }

        // 验证医生账户状态
        String hashKey = Constants.CACHT_KEY_INFO_DOCTOR + doctorId;
        String fieldsKey = Constants.DOCTOR_STATUS_KEY;
        Object cacheStauts = RedisUtil.hashOps().hget(hashKey, fieldsKey);
        if ("1".equals(cacheStauts)) {
            throw new NaiteruiIMException(ExceptionCodes.DOCTOR_IS_DISABLE);
        }
        /*
         * 如果第一次发送则校验支付会话
         */
        if (sessionVo == null) {
            SessionVO vo = sessionHandler.handleSession(medicalRecordMessage, false);
            /*
             * 支付失败或者未确认支付结果情况下返回给前端
             */
            if (vo != null && vo.getConsultPayStatus() != null) {
                if (vo.getConsultPayStatus() == Constants.SESSION_PAY_STATUS_FAIL
                        || vo.getConsultPayStatus() == Constants.SESSION_PAY_STATUS_PAYMENT_IN) {
                    /*
                     * 缓存待发送病历
                     */
                    RedisUtil.valueOps().set(Constants.CACHE_PATIENTS_MEDICAL_RECORD_TOSEND_KEY + patientId + "_" + doctorId, caseId,
                            PATIENTS_RECORD_TOBE_SEND_EXPIRE_TIME);
                    return NEED_PAY_CONSULT;
                }
            }
            if (vo != null && vo.isFinished()) {
                return NEED_PAY_CONSULT;
            }
        }

        Map<String, String> medicalSyncParamMap = new HashMap<>();
        medicalSyncParamMap.put("patientId", patientId.toString());
        medicalSyncParamMap.put("caseId", caseId);

        /*
         * 调用病历服务接口,同步患者病历 让医生可见
         */
        String recordId = RestHttpUtil.post()
                                      .url(remoterConfig.getPatientsMedicalSyncUrl())
                                      .paramMap(medicalSyncParamMap)
                                      .executeData(new ParameterizedTypeReference<ResponseVO<String>>() {
                                      });
        medicalRecordMessage.setContent(createPatientMedicalRecordMessagae(recordId, patientId.toString()));
        // 消息存储、离线消息、消息发送、咨询积分处理等逻辑
        messageHandler.handleMessage(medicalRecordMessage);
        /*
         * 发送完缓存的病历之后，删除待发送的 缓存
         */
        if (sessionVo != null
                && RedisUtil.keyOps().existsKey(Constants.CACHE_PATIENTS_MEDICAL_RECORD_TOSEND_KEY + patientId + "_" + doctorId)) {
            RedisUtil.keyOps().delete(Constants.CACHE_PATIENTS_MEDICAL_RECORD_TOSEND_KEY + patientId + "_" + doctorId);
        }
        return MEDICAL_RECORD_SEND_SUCCESS;
    }

    private CustomizedMessage createPatientMedicalRecordMessagae(String caseId, String patientId) {
        CustomizedMessage customizedMessage = new CustomizedMessage();
        CustomizedMessage.HrefContent hrefContent = new CustomizedMessage.HrefContent();
        CustomizedMessage.HrefContent.Params caseIdParam = new HrefContent.Params("recoreId", caseId);
        CustomizedMessage.HrefContent.Params patientIdParam = new HrefContent.Params("patientId", patientId);
        List<Params> paramsList = new ArrayList<>();
        paramsList.add(caseIdParam);
        paramsList.add(patientIdParam);
        hrefContent.setParams(paramsList);
        hrefContent.setK(AppJumpProtocol.IM_PATIENTS_MEDICAL_RECORD_DETAIL.getK());
        hrefContent.setV(remoterConfig.getPatientsMedicalDetailUrl());
        String text = String.format(messageConfig.getPatientsMedicalRecord(), JSON.toJSONString(hrefContent));// text
        customizedMessage.setText(text);
        customizedMessage.setSpecificMessageType(CustomizedMessage.SPEC_MESS_TYPE_PATIENTS_MEDICAL_RECORD);
        return customizedMessage;
    }

    @Override
    public void sendPatientsMedicalRecordFillReminder(Long doctorId, Long patientId) throws NaiteruiIMException {
        remoter.sendMsgToWx(null, null, Constants.PATIENTS_MEDICAL_FILL_REMINDER, doctorId, patientId, null, null, true);
    }

    @Override
    public MessageSendResultBO sendDoctorsMedicalRecord(DoctorsMedicalRecordBO doctorsMedicalRecordBO) throws NaiteruiIMException {
        MessageSendResultBO result = new MessageSendResultBO();
        Message<DoctorsMedicalRecord> medicalRecordMessage = new Message<>();
        medicalRecordMessage.setFrom(new Chater(doctorsMedicalRecordBO.getDoctorId()));
        medicalRecordMessage.setTo(new Chater(doctorsMedicalRecordBO.getPatientId()));
        medicalRecordMessage.setRelation(Constants.MESSAGE_RESOURCE_DOCTOR);
        medicalRecordMessage.setInquirerId(doctorsMedicalRecordBO.getInquirerId());
        medicalRecordMessage.setSysMsgType(Message.SYS_MSG_TYPE_CONSUMER);
        medicalRecordMessage.setVisualType(doctorsMedicalRecordBO.getSend() ? Message.MESSAGE_VISUAL_TYPE_ALL : Message.MESSAGE_VISUAL_TYPE_DOCTOR);
        medicalRecordMessage.setType(Constants.MESSAGE_TYPE_DOCTORS_MEDICALRECORD);

        DoctorsMedicalRecord medicalRecord = new DoctorsMedicalRecord();
        //病历处方是否有效标识-正常
        medicalRecord.setInvalid(RecommandDrug.RECOMMAND_DRUG_VALID);
        medicalRecord.setDoctorId(doctorsMedicalRecordBO.getDoctorId());
        medicalRecord.setDoctorName(doctorsMedicalRecordBO.getDoctorName());
        medicalRecord.setDoctorTitle(doctorsMedicalRecordBO.getDoctorTitle());
        medicalRecord.setPatientId(doctorsMedicalRecordBO.getPatientId());
        medicalRecord.setPatientName(doctorsMedicalRecordBO.getPatientName());
        medicalRecord.setGender(doctorsMedicalRecordBO.getPatientGender());
        medicalRecord.setAge(doctorsMedicalRecordBO.getAge());
        medicalRecord.setMainComplaint(doctorsMedicalRecordBO.getMainComplaint());
        medicalRecord.setDiagnosis(doctorsMedicalRecordBO.getDiagnosis());
        medicalRecord.setDoctorsSummary(doctorsMedicalRecordBO.getDoctorsSummary());
        medicalRecord.setRecommandId(doctorsMedicalRecordBO.getRecommandId());
        medicalRecord.setMedicalRecordId(doctorsMedicalRecordBO.getMedicalRecordId());
        medicalRecord.setPatientsMedicalRecordId(doctorsMedicalRecordBO.getPatientsMedicalRecordId());
        medicalRecord.setAuditDesc(doctorsMedicalRecordBO.getAuditDesc());
        medicalRecord.setCheckingStatus(doctorsMedicalRecordBO.getSend() ? RecommandDrug.RECOMMAND_TYPE_NO_CHECK : RecommandDrug.RECOMMAND_TYPE_IN_CHECK);
        medicalRecord.setDisplayType(doctorsMedicalRecordBO.getDisplayType());
        medicalRecord.setConsultType(doctorsMedicalRecordBO.getConsultType());
        medicalRecordMessage.setContent(medicalRecord);
        // 校准消息发送时间
        medicalRecordMessage.setSendTime(System.currentTimeMillis());

        String sessionKey = sessionHandler.getSessionKey(doctorsMedicalRecordBO.getDoctorId(), doctorsMedicalRecordBO.getPatientId());
        Long consultSessionId = NumberUtils.toLong(RedisUtil.hashOps().hget(sessionKey, "consultSessionId"));
        Integer consultSourceType = NumberUtils.toInt(RedisUtil.hashOps().hget(sessionKey, "consultSourceType"));
        // 是否有存在的会话
        boolean isSession = RedisUtil.keyOps().existsKey(sessionKey);
        // 是否首次绑定
        boolean isFirstBind = RedisUtil.keyOps().existsKey(CommonConstant.PATIENT_PERSONAL_DOCTOR_FIRST_BIND + doctorsMedicalRecordBO.getDoctorId() + "_" + doctorsMedicalRecordBO.getPatientId());
        LOGGER.info("医生给患者推送病历是否需要开会话，consultType：{}， isSession：{}, isFirstBind：{}", doctorsMedicalRecordBO.getConsultType(), isSession, isFirstBind);
        // 图文问诊和有会话的或者首次绑定才会进行会话处理
        if (CommonConstant.ConsultType.GRAPHIC == doctorsMedicalRecordBO.getConsultType() && (isSession || isFirstBind)) {
            String sessionId = RedisUtil.hashOps().hget(sessionKey, "sessionId");
            Long beginTime = NumberUtils.toLong(RedisUtil.hashOps().hget(sessionKey, "beginTime"));

        /*SessionVO vo;
        try {
            vo = this.sessionHandler.handleSession(medicalRecordMessage, true);
        } catch (NaiteruiIMException e) {
            LOGGER.error("sendDoctorsMedicalRecord handleSession 医生发送病历异常", e);
            throw new NaiteruiIMException(ExceptionCodes.DOCTOR_MEDICALRECORD_SEND_ERROR);
        }

            if (vo == null || vo.isFinished()) {
                return result;
            }*/

            try {
                // 保存病历消息 to 2021-05-27
                // 进行session处理 视频问诊的病历不开会话
                sessionHandler.handleSession(medicalRecordMessage);

                messageHandler.handleMessage(medicalRecordMessage);

                // 通过 审核后给患者发消息
                //            doctorMessageHandler.sendMessage(medicalRecordMessage);
                //            // 推药处方和规划
                //            doctorMessageHandler.afterSendMessage(medicalRecordMessage);
                //            // 发送消息到医生端
                //            doctorMessageHandler.sendSyncMessage(medicalRecordMessage);
                //            sessionHandler.updateContains(medicalRecordMessage.getSession().getSessionId(), null, null);

                //            doctorMessageHandler.sendMessage(medicalRecordMessage);
            } catch (Exception e) {
                LOGGER.error("sendDoctorsMedicalRecord handleMessage 医生发送病历异常", e);
                throw new NaiteruiIMException(ExceptionCodes.DOCTOR_MEDICALRECORD_SEND_ERROR);
            }
            result.setBeginTime(beginTime);
            result.setSessionId(sessionId);
        } else {
            try {
                messageHandler.handleMessage(medicalRecordMessage);
            } catch (Exception e) {
                LOGGER.error("sendDoctorsMedicalRecord handleMessage 医生发送病历异常", e);
                throw new NaiteruiIMException(ExceptionCodes.DOCTOR_MEDICALRECORD_SEND_ERROR);
            }
        }
        result.setConsultSessionId(consultSessionId);
        result.setMessageId(medicalRecordMessage.getId());
        result.setConsultSourceType(consultSourceType);
        result.setSendTime(medicalRecordMessage.getSendTime());
        //result.setNewSessionFlag(vo.getNewSessionFlag());
        //result.setConsultPayType(medicalRecordMessage.getSession().getConsultPayType());
        //result.setConsultSourceType(vo.getConsultSourceType());
        return result;
    }


    @Override
    public PatientMsgSendBO sendPatientMsg(PatientMsgSendParamBO msgSendParamBO) throws BusinessException {

        // 生成一条新的视频消息
        Chater from = new Chater();
        from.setId(msgSendParamBO.getPatientId());
        Chater to = new Chater();
        to.setId(msgSendParamBO.getDoctorId());

        Message message;
        Integer msgType = msgSendParamBO.getMsgType();
        LOGGER.info("sendPatientMsg msgType: {}", msgType);
        switch (msgType) {
            case PatientMsgSendParamBO.MsgType.TEXT:
                message = new Message<String>();
                message.setType(Constants.MESSAGE_TYPE_TEXT);
                message.setContent(msgSendParamBO.getContent());
                break;
            case PatientMsgSendParamBO.MsgType.IMAGE:
                message = new Message<Media>();
                message.setType(Constants.MESSAGE_TYPE_MEDIA_IMG);
                message.setContent(new Media(msgSendParamBO.getPath(), msgSendParamBO.getTimeLength(), msgSendParamBO.getSize()));
                break;
            case PatientMsgSendParamBO.MsgType.VOICE:
                message = new Message<Media>();
                message.setType(Constants.MESSAGE_TYPE_MEDIA_AUDIO);
                message.setContent(new Media(msgSendParamBO.getPath(), msgSendParamBO.getTimeLength(), msgSendParamBO.getSize()));
                break;
            case PatientMsgSendParamBO.MsgType.VIDEO:
            case PatientMsgSendParamBO.MsgType.SHORTVIDEO:
                message = new Message<Media>();
                message.setType(Constants.MESSAGE_TYPE_MEDIA_VIDEO);
                message.setContent(new Media(msgSendParamBO.getPath(), msgSendParamBO.getTimeLength(), msgSendParamBO.getSize()));
                break;
            case PatientMsgSendParamBO.MsgType.MEDICALRECORD:
                message = new Message<CustomizedMessage>();
                message.setType(Constants.MESSAGE_TYPE_REMIND_PATIENTS_FILL_MEDICAL_RECORD);
                CustomizedMessage medicalRecordMessage = new CustomizedMessage();
                CustomizedMessage.HrefContent medicalRecordContent = new CustomizedMessage.HrefContent();
                CustomizedMessage.HrefContent.Params caseIdParam = new HrefContent.Params("recoreId", msgSendParamBO.getExtId());
                CustomizedMessage.HrefContent.Params patientIdParam = new HrefContent.Params("patientId", msgSendParamBO.getPatientId().toString());
                medicalRecordContent.setParams(Arrays.asList(caseIdParam, patientIdParam));

                medicalRecordContent.setK(AppJumpProtocol.IM_PATIENTS_MEDICAL_RECORD_DETAIL.getK());
                medicalRecordContent.setV(remoterConfig.getPatientsMedicalDetailUrl());
                medicalRecordMessage.setSpecificMessageType(CustomizedMessage.SPEC_MESS_TYPE_PATIENTS_MEDICAL_RECORD);
                medicalRecordMessage.setText(msgSendParamBO.getContent() + String.format("<br/><a href='%s'><font " +
                    "color='#367dff'>点击查看详情</font></a>", JSON
                                                                                                 .toJSONString(medicalRecordContent)));
                message.setContent(medicalRecordMessage);
                message.setVisualType(Message.MESSAGE_VISUAL_TYPE_DOCTOR);
                break;
            case PatientMsgSendParamBO.MsgType.PATIENTDISEASE:
                message = new Message<CustomizedMessage>();
                message.setType(Constants.MESSAGE_TYPE_REMIND_PATIENTS_FILL_MEDICAL_RECORD);
                CustomizedMessage diseaseMessage = new CustomizedMessage();
                CustomizedMessage.HrefContent diseaseRecordContent = new CustomizedMessage.HrefContent();
                CustomizedMessage.HrefContent.Params diseaseId = new HrefContent.Params("diseaseId", msgSendParamBO.getExtId());
                CustomizedMessage.HrefContent.Params diseasePatientId = new HrefContent.Params("patientId", msgSendParamBO.getPatientId().toString());
                diseaseRecordContent.setParams(Arrays.asList(diseaseId, diseasePatientId));

                diseaseRecordContent.setK(AppJumpProtocol.IM_PATIENTS_DISEASE_RECORD_DETAIL.getK());
                diseaseRecordContent.setV(remoterConfig.getPatientsDiseaseDetailUrl());
                diseaseMessage.setSpecificMessageType(CustomizedMessage.SPEC_MESS_TYPE_PATIENTS_DISEASE_RECORD);
                diseaseMessage.setText(msgSendParamBO.getContent()
                    + String.format("<br/><a href='%s'><font color='#367dff'>点击查看详情</font></a>",
                                                                                   JSON.toJSONString(diseaseRecordContent)));
                diseaseMessage.setDiseaseId(msgSendParamBO.getExtId());
                message.setContent(diseaseMessage);
                Extend extend = new Extend();
                extend.setDiseaseId(msgSendParamBO.getExtId());
                message.setExd(extend);
                message.setVisualType(Message.MESSAGE_VISUAL_TYPE_DOCTOR);
                break;
            case PatientMsgSendParamBO.MsgType.CLINICFIRSTCONSULT:
                LOGGER.info("专家名医首次咨询发送提示文本消息！");
                // 专家名医首次咨询消息提醒
                message = new Message<CustomizedMessage>();
                message.setType(Constants.MESSAGE_TYPE_REMIND_PATIENTS_FILL_MEDICAL_RECORD);
                CustomizedMessage clinicFirstMessage = new CustomizedMessage();
                clinicFirstMessage.setText(msgSendParamBO.getContent() + "<font " +
                                                   "color='#D78690'>咨询来自「专家名医」，请根据患者病情合理引导患者线下初诊</font>");
                clinicFirstMessage.setSpecificMessageType(CustomizedMessage.SPEC_MESS_TYPE_PATIENTS_CLINICFIRST_RECORD);
                message.setContent(clinicFirstMessage);
                message.setVisualType(Message.MESSAGE_VISUAL_TYPE_DOCTOR);
                break;
            default:
                throw new NaiteruiIMException(ExceptionCodes.MSG_NOT_COMPLETE);
        }

        message.setFrom(from);
        message.setTo(to);
        message.setInquirerId(msgSendParamBO.getInquirerId());
        message.setRelation(Constants.MESSAGE_RESOURCE_PATIENT);
        message.setSendTime(System.currentTimeMillis());
        message.setSysMsgType(Message.SYS_MSG_TYPE_CONSUMER);
        message.setVisualType(message.getVisualType() == null ? Message.MESSAGE_VISUAL_TYPE_ALL : message.getVisualType());
        message.setLastPlatform(HeaderConstant.HEADER_ORIGIN_PATIENT_WX);

        /*boolean existMedicalCache = RedisUtil.hashOps().hexist(CommonConstant.CONSULT_MEDICAL_RECORD_EXIST, msgSendParamBO.getPatientId() + "_" +
         msgSendParamBO.getDoctorId());
        //医患从未产生过病历，需要等待医生首次填写病历
        if (!existMedicalCache) {
            return PatientMsgSendBO.builder().result(false).first(true).disclaimer(false).build();
        }*/

        /*
         * 处理会话
         */
        SessionVO vo = sessionHandler.handleSession(message, false);

        if (vo == null || vo.isFinished()) {
            return PatientMsgSendBO.builder().result(false).closed(true).build();
        }

        /*
         * 支付失败或者未确认支付结果情况下返回给前端
         */
        if (vo.getConsultPayStatus() != null && Arrays.asList(Constants.SESSION_PAY_STATUS_FAIL, Constants.SESSION_PAY_STATUS_PAYMENT_IN)
                                                      .contains(vo.getConsultPayStatus())) {
            return PatientMsgSendBO.builder().result(false).doctorName(vo.getDoctorName()).consultPrice(vo.getConsultPrice()).build();
        }

        // 如果消息类型为病例或者病情，则更新当前session的firstMsg
        if (msgType == PatientMsgSendParamBO.MsgType.MEDICALRECORD || msgType == PatientMsgSendParamBO.MsgType.PATIENTDISEASE) {
            SessionDetails.Msg firstMsg = new SessionDetails.Msg();
            firstMsg.setType(message.getType());
            firstMsg.setContent(message.getContent());
            sessionDao.updateFirstMsg(vo.getSessionId(), firstMsg);
        }

        /*
         * 处理消息推送
         */
        messageHandler.handleMessage(message);

        return PatientMsgSendBO
                .builder()
                .result(true)
                .first(Optional.ofNullable(vo.getIsFirst()).map(o -> o == 1).orElse(false))
                .build();
    }

    @Override
    public void sendServiceEvaluation(String message) throws BusinessException {
        // 解码消息对象
        Message<?> messageBean = this.messageTransCoder.decodeMessage(message);
        // 校准消息发送时间
        messageBean.setSendTime(System.currentTimeMillis());
        messageBean.setVisualType(ImConstants.MESSAGE_VISUAL_TYPE_PATIENT);
        messageBean.setSysMsgType(Message.SYS_MSG_TYPE_SYSTEM);
        // 设置消息来源
        messageBean.setRelation(HeaderConstant.HEADER_ORIGIN_DOCTOR_APP);
        LOGGER.info("服务评价消息,param: {}", JSONUtil.toJsonStr(messageBean));
        // 保存消息到聊天记录
        messageDao.saveMessage(messageBean);
        doctorMessageHandler.sendMessage(messageBean);

    }

    @Override
    public void sendPatientSysMessage(PatientMsgSendParamBO msgSendParamBO) throws BusinessException {
        Long doctorId = msgSendParamBO.getDoctorId();
        Long patientId = msgSendParamBO.getPatientId();
        Long inquirerId = msgSendParamBO.getInquirerId();
        Chater from = new Chater();
        from.setId(doctorId);
        Chater to = new Chater();
        to.setId(patientId);
        Message message = new Message<String>();
        message.setContent(msgSendParamBO.getContent());
        message.setFrom(from);
        message.setTo(to);
        message.setType(msgSendParamBO.getMsgType());
        message.setVisualType(Message.MESSAGE_VISUAL_TYPE_PATIENT);
        message.setInquirerId(inquirerId);
        message.setRelation(Constants.MESSAGE_RESOURCE_DOCTOR);
        message.setSendTime(System.currentTimeMillis());
        message.setSysMsgType(Message.SYS_MSG_TYPE_SYSTEM);
        message.setVisualType(message.getVisualType() == null ? Message.MESSAGE_VISUAL_TYPE_ALL : message.getVisualType());
        message.setLastPlatform(HeaderConstant.HEADER_ORIGIN_PATIENT_WX);
        LOGGER.info("向患者发送系统消息, param: {}", JSONUtil.toJsonStr(message));
        // 保存消息到聊天记录
        messageDao.saveMessage(message);
        doctorMessageHandler.sendMessage(message);
        if (ImConstants.MESSAGE_TYPE_PATIENTS_FIRST_BIND == message.getType()) {
            Integer consultPayType = Constants.CONSULT_PAY_TYPE_FREE;// 默认初始化：免费会话
            Integer consultSourceType = SessionDetails.CONSULT_TYPE_GENERAL;// 咨询类型 0-默认普通会话
            String sessionKey = sessionHandler.getSessionKey(doctorId, patientId);
            // 生成sessionId
            String sessionId = sessionKey + "_" + System.currentTimeMillis();
            LOGGER.info("患者首次扫码绑定医生免费会话开启 sessionId：{}", sessionId);
            // 切换该医生为当前咨询医生
            RedisUtil.hashOps().hset(CommonConstant.CONSULT_PATIENT_AIM, patientId.toString(), doctorId.toString());
            RedisUtil.hashOps().hset(CommonConstant.CONSULT_PATIENT_AIM_NAME, patientId.toString(), doctorId.toString());
            RedisUtil.hashOps().hset(CommonConstant.CONSULT_PATIENT_AIM_INQUIRER, String.join("_", doctorId.toString(), patientId.toString()), inquirerId.toString());
            message.setInquirerId(inquirerId);
            RedisUtil.hashOps().hsetnx(sessionKey, "sessionId", sessionId);
            // 创建新的session,并返回结果
            sessionHandler.createSession(sessionKey, sessionId, message, consultPayType, consultSourceType);
        }
    }

    @Override
    public void sendFollowUpMessage(FollowUpImMsgBO followUpImMsg) throws BusinessException {
        Message<FollowUpImMsg> followUpImMsgMessage = new Message<>();
        followUpImMsgMessage.setFrom(new Chater(followUpImMsg.getDoctorId()));
        followUpImMsgMessage.setTo(new Chater(followUpImMsg.getPatientId()));
        followUpImMsgMessage.setRelation(Constants.MESSAGE_RESOURCE_DOCTOR);
        followUpImMsgMessage.setInquirerId(followUpImMsg.getInquirerId());
        followUpImMsgMessage.setSysMsgType(Message.SYS_MSG_TYPE_CONSUMER);
        followUpImMsgMessage.setVisualType(Message.MESSAGE_VISUAL_TYPE_ALL);
        followUpImMsgMessage.setType(ImConstants.MESSAGE_TYPE_FOLLOW_UP);
        followUpImMsgMessage.setContent(FollowUpImMsg.builder().doctorId(followUpImMsg.getDoctorId())
            .patientId(followUpImMsg.getPatientId()).inquirerId(followUpImMsg.getInquirerId())
            .followUpId(followUpImMsg.getFollowUpId()).followUpName(followUpImMsg.getFollowUpName())
            .beginTime(followUpImMsg.getBeginTime()).followUpRemind(followUpImMsg.getFollowUpRemind())
            .type(followUpImMsg.getType()).followUpNo(followUpImMsg.getFollowUpNo()).build());
        // 校准消息发送时间
        followUpImMsgMessage.setSendTime(System.currentTimeMillis());
        LOGGER.info("发送随访卡片消息：{}", JsonMapper.toJson(followUpImMsgMessage));
        String sessionKey = sessionHandler.getSessionKey(followUpImMsg.getDoctorId(), followUpImMsg.getPatientId());
        // 是否有存在的会话
        boolean isSession = RedisUtil.keyOps().existsKey(sessionKey);
        // 有会话的才会进行会话处理
        if (isSession) {
            try {
                // 进行session处理
                sessionHandler.handleSession(followUpImMsgMessage);
                messageHandler.handleMessage(followUpImMsgMessage);
            } catch (Exception e) {
                LOGGER.error("sendFollowUpMessage handleMessage 医生发送随访异常", e);
                throw new NaiteruiIMException(ExceptionCodes.FOLLOW_UP_SEND_ERROR);
            }
        } else {
            try {
                messageHandler.handleMessage(followUpImMsgMessage);
            } catch (Exception e) {
                LOGGER.error("sendFollowUpMessage handleMessage 医生发送随访异常", e);
                throw new NaiteruiIMException(ExceptionCodes.FOLLOW_UP_SEND_ERROR);
            }
        }
    }
}
